package ophiux.alipay.template.sign;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.crypto.Cipher;

/**
 * 
 * 说明：支付宝签名工具类<br>
 * <h1>创 建 人: hehailong   <br></h1>
 * 创建日期: 2017-9-19 下午2:48:14<br>
 * 需要的jar包:
 */
public class AlipaySignature {
	private static final int MAX_ENCRYPT_BLOCK = 117;
	private static final int MAX_DECRYPT_BLOCK = 128;

	/**
	 * 参数签名入口
	 * @author hhl
	 * @param requestHolder
	 * @return
	 */
	public static String getSignatureContent(
			RequestParametersHolder requestHolder) {
		return getSignContent(getSortedMap(requestHolder));
	}
	
	/**
	 * 将MAP中的值拼接成 get请求参数     如：biz_content={"touser":"2088012785939622","template":{"template_id":"73f826befbec4a7fb9ae4e557aa53193","context":{"headColor":"#0066CC","url":"","actionName":"查看详情","first":{"value":"您申请打印的病历有新的动态","color":"#CC3333"},"keyword1":{"value":"800.00元","color":"#0066CC"},"keyword2":{"value":"检查化验费","color":"#0066CC"},"remark":{"value":"感觉你的使用。","color":"#CCCC"}}}}&timestamp=2017-09-05 08:08:08&sign_type=RSA2&charset=UTF-8&app_id=2016092201947313&format=JSON&version=1.0
	 * @param params 	  参数
	 * @param charset 	编码格式
	 * @return
	 * @throws IOException
	 */
	public static String buildQuery(Map<String, String> params)
			throws IOException {
		if ((params == null) || (params.isEmpty())) {
			return null;
		}

		StringBuilder query = new StringBuilder();
		Set entries = params.entrySet();
		boolean hasParam = false;

		for (Iterator i$ = entries.iterator(); i$.hasNext();) {
			Map.Entry entry = (Map.Entry) i$.next();
			String name = (String) entry.getKey();
			String value = (String) entry.getValue();

			if (StringUtils.areNotEmpty(new String[] { name, value })) {
				if (hasParam)
					query.append("&");
				else {
					hasParam = true;
				}
				query.append(name).append("=").append(value);
			}
		}
		return query.toString();
	}

	public static Map<String, String> getSortedMap(
			RequestParametersHolder requestHolder) {
		Map sortedParams = new TreeMap();
		AlipayHashMap appParams = requestHolder.getApplicationParams();
		if ((appParams != null) && (appParams.size() > 0))
			sortedParams.putAll(appParams);

		AlipayHashMap protocalMustParams = requestHolder
				.getProtocalMustParams();
		if ((protocalMustParams != null) && (protocalMustParams.size() > 0))
			sortedParams.putAll(protocalMustParams);

		AlipayHashMap protocalOptParams = requestHolder.getProtocalOptParams();
		if ((protocalOptParams != null) && (protocalOptParams.size() > 0)) {
			sortedParams.putAll(protocalOptParams);
		}

		return sortedParams;
	}

	/**
	 * 将MAP中的签名内容进行排序
	 * @param sortedParams  
	 * @return	String
	 */
	public static String getSignContent(Map<String, String> sortedParams) {
		StringBuffer content = new StringBuffer();
		List keys = new ArrayList(sortedParams.keySet());
		Collections.sort(keys);
		int index = 0;
		for (int i = 0; i < keys.size(); ++i) {
			String key = (String) keys.get(i);
			String value = (String) sortedParams.get(key);
			if (StringUtils.areNotEmpty(new String[] { key, value })) {
				content.append(((index == 0) ? "" : "&") + key + "=" + value);
				++index;
			}
		}
		return content.toString();
	}
	
	/**
	 * 支付宝签名  通用方法
	 * @param content	签名内容
	 * @param privateKey	私钥
	 * @param charset	编码格式   utf-8,gbk
	 * @param signType 签名类型  RSA,RSA2
	 * @return	String
	 * @throws AlipayApiException
	 */
	public static String rsaSign(String content, String privateKey,
			String charset, String signType) throws AlipayApiException {
		if ("RSA".equals(signType)) {
			return rsaSign(content, privateKey, charset);
		}
		if ("RSA2".equals(signType)) {
			return rsa256Sign(content, privateKey, charset);
		}

		throw new AlipayApiException("Sign Type is Not Support : signType="
				+ signType);
	}
	/**
	 * RSA2签名
	 * @param content	签名内容
	 * @param privateKey	私钥
	 * @param charset	编码格式   utf-8,gbk
	 * @return String
	 * @throws AlipayApiException
	 */
	public static String rsa256Sign(String content, String privateKey,
			String charset) throws AlipayApiException {
		PrivateKey priKey;
		try {
			priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(privateKey.getBytes()));
			Signature signature = Signature.getInstance("SHA256WithRSA");
			signature.initSign(priKey);
			if (StringUtils.isEmpty(charset))
				signature.update(content.getBytes());
			else {
				signature.update(content.getBytes(charset));
			}

			byte[] signed = signature.sign();

			return new String(Base64.encodeBase64(signed));
		} catch (Exception e) {
			throw new AlipayApiException("RSAcontent = " + content+ "; charset = " + charset, e);
		}
	}

	public static String rsaSign(String content, String privateKey,
			String charset) throws AlipayApiException {
		PrivateKey priKey;
		try {
			priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(
					privateKey.getBytes()));

			Signature signature = Signature.getInstance("SHA1WithRSA");

			signature.initSign(priKey);

			if (StringUtils.isEmpty(charset))
				signature.update(content.getBytes());
			else {
				signature.update(content.getBytes(charset));
			}

			byte[] signed = signature.sign();

			return new String(Base64.encodeBase64(signed));
		} catch (InvalidKeySpecException ie) {
			throw new AlipayApiException("RSA私钥格式不正确，请检查是否正确配置了PKCS8格式的私钥", ie);
		} catch (Exception e) {
			throw new AlipayApiException("RSAcontent = " + content
					+ "; charset = " + charset, e);
		}
	}

	public static String rsaSign(Map<String, String> params, String privateKey,
			String charset) throws AlipayApiException {
		String signContent = getSignContent(params);

		return rsaSign(signContent, privateKey, charset);
	}

	public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
			InputStream ins) throws Exception {
		if ((ins == null) || (StringUtils.isEmpty(algorithm))) {
			return null;
		}

		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

		byte[] encodedKey = StreamUtil.readText(ins).getBytes();

		encodedKey = Base64.decodeBase64(encodedKey);

		return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
	}

	public static String getSignCheckContentV1(Map<String, String> params) {
		if (params == null) {
			return null;
		}

		params.remove("sign");
		params.remove("sign_type");

		StringBuffer content = new StringBuffer();
		List keys = new ArrayList(params.keySet());
		Collections.sort(keys);

		for (int i = 0; i < keys.size(); ++i) {
			String key = (String) keys.get(i);
			String value = (String) params.get(key);
			content.append(((i == 0) ? "" : "&") + key + "=" + value);
		}

		return content.toString();
	}

	public static String getSignCheckContentV2(Map<String, String> params) {
		if (params == null) {
			return null;
		}

		params.remove("sign");

		StringBuffer content = new StringBuffer();
		List keys = new ArrayList(params.keySet());
		Collections.sort(keys);

		for (int i = 0; i < keys.size(); ++i) {
			String key = (String) keys.get(i);
			String value = (String) params.get(key);
			content.append(((i == 0) ? "" : "&") + key + "=" + value);
		}

		return content.toString();
	}

	public static boolean rsaCheckV1(Map<String, String> params,
			String publicKey, String charset) throws AlipayApiException {
		String sign = (String) params.get("sign");
		String content = getSignCheckContentV1(params);

		return rsaCheckContent(content, sign, publicKey, charset);
	}

	public static boolean rsaCheckV1(Map<String, String> params,
			String publicKey, String charset, String signType)
			throws AlipayApiException {
		String sign = (String) params.get("sign");
		String content = getSignCheckContentV1(params);

		return rsaCheck(content, sign, publicKey, charset, signType);
	}

	public static boolean rsaCheckV2(Map<String, String> params,
			String publicKey, String charset) throws AlipayApiException {
		String sign = (String) params.get("sign");
		String content = getSignCheckContentV2(params);

		return rsaCheckContent(content, sign, publicKey, charset);
	}

	public static boolean rsaCheckV2(Map<String, String> params,
			String publicKey, String charset, String signType)
			throws AlipayApiException {
		String sign = (String) params.get("sign");
		String content = getSignCheckContentV2(params);

		return rsaCheck(content, sign, publicKey, charset, signType);
	}

	public static boolean rsaCheck(String content, String sign,
			String publicKey, String charset, String signType)
			throws AlipayApiException {
		if ("RSA".equals(signType)) {
			return rsaCheckContent(content, sign, publicKey, charset);
		}
		if ("RSA2".equals(signType)) {
			return rsa256CheckContent(content, sign, publicKey, charset);
		}

		throw new AlipayApiException("Sign Type is Not Support : signType="
				+ signType);
	}

	public static boolean rsa256CheckContent(String content, String sign,
			String publicKey, String charset) throws AlipayApiException {
		PublicKey pubKey;
		try {
			pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(
					publicKey.getBytes()));

			Signature signature = Signature.getInstance("SHA256WithRSA");

			signature.initVerify(pubKey);

			if (StringUtils.isEmpty(charset))
				signature.update(content.getBytes());
			else {
				signature.update(content.getBytes(charset));
			}

			return signature.verify(Base64.decodeBase64(sign.getBytes()));
		} catch (Exception e) {
			throw new AlipayApiException("RSAcontent = " + content + ",sign="
					+ sign + ",charset = " + charset, e);
		}
	}

	public static boolean rsaCheckContent(String content, String sign,
			String publicKey, String charset) throws AlipayApiException {
		PublicKey pubKey;
		try {
			pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(
					publicKey.getBytes()));

			Signature signature = Signature.getInstance("SHA1WithRSA");

			signature.initVerify(pubKey);

			if (StringUtils.isEmpty(charset))
				signature.update(content.getBytes());
			else {
				signature.update(content.getBytes(charset));
			}

			return signature.verify(Base64.decodeBase64(sign.getBytes()));
		} catch (Exception e) {
			throw new AlipayApiException("RSAcontent = " + content + ",sign="
					+ sign + ",charset = " + charset, e);
		}
	}

	public static PublicKey getPublicKeyFromX509(String algorithm,
			InputStream ins) throws Exception {
		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

		StringWriter writer = new StringWriter();
		StreamUtil.io(new InputStreamReader(ins), writer);

		byte[] encodedKey = writer.toString().getBytes();

		encodedKey = Base64.decodeBase64(encodedKey);

		return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
	}

	public static String checkSignAndDecrypt(Map<String, String> params,
			String alipayPublicKey, String cusPrivateKey, boolean isCheckSign,
			boolean isDecrypt) throws AlipayApiException {
		String charset = (String) params.get("charset");
		String bizContent = (String) params.get("biz_content");
		if ((isCheckSign) && (!(rsaCheckV2(params, alipayPublicKey, charset)))) {
			throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
		}

		if (isDecrypt) {
			return rsaDecrypt(bizContent, cusPrivateKey, charset);
		}

		return bizContent;
	}

	public static String checkSignAndDecrypt(Map<String, String> params,
			String alipayPublicKey, String cusPrivateKey, boolean isCheckSign,
			boolean isDecrypt, String signType) throws AlipayApiException {
		String charset = (String) params.get("charset");
		String bizContent = (String) params.get("biz_content");
		if ((isCheckSign)
				&& (!(rsaCheckV2(params, alipayPublicKey, charset, signType)))) {
			throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
		}

		if (isDecrypt) {
			return rsaDecrypt(bizContent, cusPrivateKey, charset);
		}

		return bizContent;
	}

	public static String encryptAndSign(String bizContent,
			String alipayPublicKey, String cusPrivateKey, String charset,
			boolean isEncrypt, boolean isSign) throws AlipayApiException {
		StringBuilder sb = new StringBuilder();
		if (StringUtils.isEmpty(charset))
			charset = "GBK";

		sb.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
		if (isEncrypt) {
			sb.append("<alipay>");
			String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
			sb.append("<response>" + encrypted + "</response>");
			sb.append("<encryption_type>RSA</encryption_type>");
			if (isSign) {
				String sign = rsaSign(encrypted, cusPrivateKey, charset);
				sb.append("<sign>" + sign + "</sign>");
				sb.append("<sign_type>RSA</sign_type>");
			}
			sb.append("</alipay>");
		} else if (isSign) {
			sb.append("<alipay>");
			sb.append("<response>" + bizContent + "</response>");
			String sign = rsaSign(bizContent, cusPrivateKey, charset);
			sb.append("<sign>" + sign + "</sign>");
			sb.append("<sign_type>RSA</sign_type>");
			sb.append("</alipay>");
		} else {
			sb.append(bizContent);
		}
		return sb.toString();
	}

	public static String encryptAndSign(String bizContent,
			String alipayPublicKey, String cusPrivateKey, String charset,
			boolean isEncrypt, boolean isSign, String signType)
			throws AlipayApiException {
		StringBuilder sb = new StringBuilder();
		if (StringUtils.isEmpty(charset))
			charset = "GBK";

		sb.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
		if (isEncrypt) {
			sb.append("<alipay>");
			String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
			sb.append("<response>" + encrypted + "</response>");
			sb.append("<encryption_type>RSA</encryption_type>");
			if (isSign) {
				String sign = rsaSign(encrypted, cusPrivateKey, charset,
						signType);
				sb.append("<sign>" + sign + "</sign>");
				sb.append("<sign_type>");
				sb.append(signType);
				sb.append("</sign_type>");
			}
			sb.append("</alipay>");
		} else if (isSign) {
			sb.append("<alipay>");
			sb.append("<response>" + bizContent + "</response>");
			String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
			sb.append("<sign>" + sign + "</sign>");
			sb.append("<sign_type>");
			sb.append(signType);
			sb.append("</sign_type>");
			sb.append("</alipay>");
		} else {
			sb.append(bizContent);
		}
		return sb.toString();
	}

	public static String rsaEncrypt(String content, String publicKey,
			String charset) throws AlipayApiException {
		PublicKey pubKey;
		try {
			pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(
					publicKey.getBytes()));

			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(1, pubKey);
			byte[] data = (StringUtils.isEmpty(charset)) ? content.getBytes()
					: content.getBytes(charset);

			int inputLen = data.length;
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int offSet = 0;

			int i = 0;

			while (inputLen - offSet > 0) {
				byte[] cache;
				if (inputLen - offSet > 117)
					cache = cipher.doFinal(data, offSet, 117);
				else
					cache = cipher.doFinal(data, offSet, inputLen - offSet);

				out.write(cache, 0, cache.length);
				++i;
				offSet = i * 117;
			}
			byte[] encryptedData = Base64.encodeBase64(out.toByteArray());
			out.close();

			return new String(encryptedData, charset);
		} catch (Exception e) {
			throw new AlipayApiException("EncryptContent = " + content
					+ ",charset = " + charset, e);
		}
	}

	public static String rsaDecrypt(String content, String privateKey,
			String charset) throws AlipayApiException {
		PrivateKey priKey;
		try {
			priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(
					privateKey.getBytes()));

			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(2, priKey);
			byte[] encryptedData = (StringUtils.isEmpty(charset)) ? Base64
					.decodeBase64(content.getBytes()) : Base64
					.decodeBase64(content.getBytes(charset));

			int inputLen = encryptedData.length;
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int offSet = 0;

			int i = 0;

			while (inputLen - offSet > 0) {
				byte[] cache;
				if (inputLen - offSet > 128)
					cache = cipher.doFinal(encryptedData, offSet, 128);
				else
					cache = cipher.doFinal(encryptedData, offSet, inputLen
							- offSet);

				out.write(cache, 0, cache.length);
				++i;
				offSet = i * 128;
			}
			byte[] decryptedData = out.toByteArray();
			out.close();

			return new String(decryptedData, charset);
		} catch (Exception e) {
			throw new AlipayApiException("EncodeContent = " + content
					+ ",charset = " + charset, e);
		}
	}
	
	
}